package redisson

import (
	"errors"
	"github.com/go-redis/redis"
	"github.com/sirupsen/logrus"
	"sync"
	"time"
)

const (
	// MaximumResponseTimeLimit 最大响应时间
	MaximumResponseTimeLimit = 10 * time.Second
)

// redLockScript ...
var redLockScript = redis.NewScript(`
// 首先分布式锁的KEY不能存在，如果确实不存在，那么执行hset命令（hset REDLOCK_KEY uuid+threadId 1），并通过pexpire设置失效时间（也是锁的租约时间）
if (redis.call('exists', KEYS[1]) == 0) then
	redis.call('hset', KEYS[1], ARGV[2], 1);
	redis.call('pexpire', KEYS[1], ARGV[1]);
	return nil;
end;

// 如果分布式锁的KEY已经存在，并且value也匹配，表示是当前线程持有的锁，那么重入次数加1，并且设置失效时间
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
	redis.call('hincrby', KEYS[1], ARGV[2], 1);
	redis.call('pexpire', KEYS[1], ARGV[1]);
	return nil;
	end;

// 获取分布式锁的KEY的失效时间毫秒数
return redis.call('pttl', KEYS[1]);
`)

// redLock ...
var redLock = &RedLock{
	once: &sync.Once{},
}

// RedLock ...
type RedLock struct {
	once   *sync.Once
	mutex  *sync.Mutex
	name   string
	rLocks []*RLock
}

// NewRedLock ...
func NewRedLock(name string, rLocks ...*RLock) *RedLock {
	// 赋值有效锁
	var newRLocks []*RLock
	for _, rLock := range rLocks {
		if rLock != nil {
			newRLocks = append(newRLocks, rLock)
		}
	}

	redLock.once.Do(func() {
		redLock = &RedLock{
			mutex:  &sync.Mutex{},
			name:   name,
			rLocks: newRLocks,
		}
	})
	return redLock
}

// Lock ...
func (redLock *RedLock) Lock() error {
	var errNilSli []error

	redLock.mutex.Lock()
	defer redLock.mutex.Unlock()
	for _, rLock := range redLock.rLocks {
		if rLock == nil {
			continue
		}

		startTime := time.Now()
		err := rLock.Lock(rLock.name)

		// 响应时间小于指定时间
		responseTime := time.Now().Sub(startTime)
		if err == nil && responseTime < MaximumResponseTimeLimit {
			errNilSli = append(errNilSli, err)
		}
	}

	// 过半节点加锁成功
	argsLen := len(redLock.rLocks)
	argsExpect := argsLen/2 + 1
	if len(errNilSli) >= argsExpect {
		return nil
	}

	return errors.New("lock fail")
}

// TryLock ...
func (redLock *RedLock) TryLock() error {
	var errSli []error
	var tryCh = make(chan error, 2)

	redLock.mutex.Lock()
	defer redLock.mutex.Unlock()

	rLockLen := len(redLock.rLocks)
	for i := 0; i < rLockLen; i++ {
		go func(rLock *RLock) {
			tryCh <- rLock.TryLock(rLock.name)
		}(redLock.rLocks[i])
	}

	// 堵塞等待
	for i := 0; i < rLockLen; i++ {
		ret := <-tryCh
		if ret == nil {
			errSli = append(errSli, nil)
		}
	}

	// 过半节点加锁成功
	argsLen := len(redLock.rLocks)
	argsExpect := argsLen/2 + 1
	if len(errSli) >= argsExpect {
		return nil
	}

	return errors.New("lock fail")
}

// UnLock ...
func (redLock *RedLock) UnLock() error {
	expiration := int64(LockExpiration) / 1000 / 1000

	redLock.mutex.Lock()
	defer redLock.mutex.Unlock()
	ret, err := rUnlockScript.Run(rLock.rdb, []string{rLock.name, LockReleaseChannel}, LockReleaseFlag, expiration, rLock.uniqueId()).Result()
	if err != nil {
		logrus.Error(err)
		return err
	}

	logrus.Infof("unlock name=%s field=%s code=%v msg=%s", rLock.name, rLock.uniqueId(), ret, rUnlockMsg[ret.(int64)])
	return nil
}
